package cn.coisini.security.filter;

import cn.coisini.model.common.constants.SecurityConstants;
import cn.coisini.model.common.enums.ResultEnum;
import cn.coisini.security.config.WhiteListConfig;
import cn.coisini.utils.ExceptionUtil;
import cn.coisini.utils.JwtUtil;
import cn.coisini.utils.ThreadLocalUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static cn.coisini.model.common.constants.AdminConstants.USERNAME;

/**
 * @author xiangshaw
 * Description: JWT过滤器
 */
@Log4j2
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;
    private final StringRedisTemplate stringRedisTemplate;
    private final WhiteListConfig whiteListConfig;
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Value("${token.time}")
    private Integer tokenTimeOut;


    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain)
            throws ServletException, IOException {

        String servletPath = request.getServletPath();
        log.info("当前请求路径：{}", servletPath);
        // 检查是否在白名单中
        if (whiteListConfig.getWhiteListUrls().stream().anyMatch(url -> PATH_MATCHER.match(url, servletPath))) {
            log.info("属于白名单：{}", servletPath);
            filterChain.doFilter(request, response);
            return;
        }
        // 获取token
        String token = resolveToken(request);
        // token为空
        if (StrUtil.isEmpty(token)) {
            ExceptionUtil.handleException(response, ResultEnum.TOKEN_NOT_FOUND);
            filterChain.doFilter(request, response);
            return;
        }
        // 获取用户信息
        try {
            if (StrUtil.isNotBlank(token) && SecurityContextHolder.getContext().getAuthentication() == null){
                // 检查token是否为黑名单
                boolean hasKey = Boolean.TRUE.equals(stringRedisTemplate.hasKey(SecurityConstants.BLACKLIST_TOKEN + token));
                if (hasKey){
                    ExceptionUtil.handleException(response, ResultEnum.TOKEN_EXCEPTION);
                    return;
                }
                // 检查token是否有效
                if (JwtUtil.tokenWhetherExpired(token, JwtUtil.getTokenSigningKey())){
                    ExceptionUtil.handleException(response, ResultEnum.TOKEN_EXPIRED);
                    return;
                }
                // 检查token是否需要续期
                String renewedToken = JwtUtil.checkRenewalToken(token);
                // token有效
                if (renewedToken != null) {
                    // 获取用户信息
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(JwtUtil.getTokenClaim(renewedToken, USERNAME));
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    // 将认证信息放入details
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // 存储token
                    ThreadLocalUtil.set(renewedToken);
                    // 将认证信息存入上下文
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    // 保存权限数据，设置过期时间2小时
                    stringRedisTemplate.opsForValue().set(userDetails.getUsername(),
                            JSON.toJSONString(userDetails.getAuthorities()),2, TimeUnit.HOURS);
                    log.info("用户：{} 登录认证成功,用户权限：{}", userDetails.getUsername(), userDetails.getAuthorities());
                } else {
                    ExceptionUtil.handleException(response, ResultEnum.TOKEN_INVALID);
                    return;
                }
            }
        }catch (JwtException e) {
            ExceptionUtil.handleException(response, ResultEnum.TOKEN_EXCEPTION);
            log.error("登录认证失败：", e);
            return;
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 解析token
     */
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        // 判断token是否为空，并且以Coisini 开头
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            return bearerToken.substring(SecurityConstants.TOKEN_PREFIX.length());
        }
        return null;
    }
}
